import argparse
import os

from dataclasses import dataclass
from enum import Enum
from typing import Optional

from cover_agent.settings.config_loader import get_settings


class CoverageType(Enum):
    """
    An enumeration representing different types of code coverage reports.

    Attributes:
        LCOV: Represents the LCOV coverage report format.
        COBERTURA: Represents the Cobertura coverage report format.
        JACOCO: Represents the JaCoCo coverage report format.
    """

    LCOV = "lcov"
    COBERTURA = "cobertura"
    JACOCO = "jacoco"


@dataclass
class CoverAgentConfig:
    """
    A data class representing the configuration for the CoverAgent.

    Attributes:
        source_file_path (str): Path to the source file.
        test_file_path (str): Path to the test file.
        project_root (str): Path to the root of the project.
        test_file_output_path (str): Path to the output test file.
        code_coverage_report_path (str): Path to the code coverage report file.
        test_command (str): The command to run tests and generate coverage report.
        test_command_dir (str): The directory to run the test command in.
        included_files (str): List of files to include in the coverage.
                              For example, "--included-files library1.c library2.c.".
        coverage_type (CoverageType): Type of coverage report.
        report_filepath (str): Path to the output report file.
        desired_coverage (int): The desired coverage percentage
        max_iterations (int): The maximum number of iterations.
        max_run_time_sec (int): Maximum time (in seconds) allowed for test execution.
        additional_instructions (str): Any additional instructions you wish to append at the end of the prompt.
        model (str): The API url to use for Ollama or Hugging Face.
        api_base (str): Base URL for the API.
        strict_coverage (bool): If set, Cover-Agent will return a non-zero exit code
                                if the desired code coverage is not achieved.
        run_tests_multiple_times (int): Number of times to run the tests generated by Cover Agent.
        log_db_path (str): Path to optional log database.
        branch (str): The branch to compare against when using --diff-coverage.
        use_report_coverage_feature_flag (bool): Setting this to True considers the coverage of all the files
                                                 in the coverage report.
        diff_coverage (bool): If set, Cover-Agent will only generate tests based on the diff between branches.
        run_each_test_separately (bool): Run each test separately.
        record_mode (bool): Enable LLM responses record mode for tests.
        test_command_original (str): Original test command before any modifications.
    """

    source_file_path: str
    test_file_path: str
    project_root: str
    test_file_output_path: str
    code_coverage_report_path: str
    test_command: str
    test_command_dir: str
    included_files: list[str] | None
    coverage_type: CoverageType
    report_filepath: str
    desired_coverage: int
    max_iterations: int
    max_run_time_sec: int
    additional_instructions: str
    model: str
    api_base: str
    strict_coverage: bool
    run_tests_multiple_times: int
    log_db_path: str
    branch: str
    use_report_coverage_feature_flag: bool
    diff_coverage: bool
    run_each_test_separately: bool
    record_mode: bool
    suppress_log_files: bool
    max_test_files_allowed_to_analyze: int
    look_for_oldest_unchanged_test_file: bool
    project_language: str
    test_command_original: Optional[str] = None

    @classmethod
    def from_cli_args(cls, args: argparse.Namespace) -> "CoverAgentConfig":
        """
        Create a CoverAgentConfig instance from command-line arguments.

        Args:
            args (argparse.Namespace): Parsed command-line arguments.

        Returns:
            CoverAgentConfig: A new instance of CoverAgentConfig populated with values
            from the provided arguments.
        """
        return cls(
            source_file_path=args.source_file_path,
            test_file_path=args.test_file_path,
            project_root=args.project_root,
            test_file_output_path=args.test_file_output_path,
            code_coverage_report_path=args.code_coverage_report_path,
            test_command=args.test_command,
            test_command_dir=args.test_command_dir,
            included_files=args.included_files,
            coverage_type=args.coverage_type,
            report_filepath=args.report_filepath,
            desired_coverage=args.desired_coverage,
            max_iterations=args.max_iterations,
            max_run_time_sec=args.max_run_time_sec,
            additional_instructions=args.additional_instructions,
            model=args.model,
            api_base=args.api_base,
            strict_coverage=args.strict_coverage,
            run_tests_multiple_times=args.run_tests_multiple_times,
            log_db_path=os.getenv("LOG_DB_PATH") or args.log_db_path,
            branch=args.branch,
            use_report_coverage_feature_flag=args.use_report_coverage_feature_flag,
            diff_coverage=args.diff_coverage,
            run_each_test_separately=args.run_each_test_separately,
            record_mode=args.record_mode,
            suppress_log_files=args.suppress_log_files,
            max_test_files_allowed_to_analyze=args.max_test_files_allowed_to_analyze,
            look_for_oldest_unchanged_test_file=args.look_for_oldest_unchanged_test_file,
            project_language=args.project_language,
        )

    @classmethod
    def from_cli_args_with_defaults(cls, args: argparse.Namespace) -> "CoverAgentConfig":
        """
        Create a CoverAgentConfig instance from command-line arguments, merging them with default settings.

        Args:
            args (argparse.Namespace): Parsed command-line arguments.

        Returns:
            CoverAgentConfig: A new instance of CoverAgentConfig populated with values from the provided
            arguments, where CLI arguments override the default settings.
        """
        default_config = get_settings().get("default")
        args_dict = vars(args)

        default_dict = {
            "source_file_path": default_config.get("source_file_path"),
            "test_file_path": default_config.get("test_file_path"),
            "project_root": default_config.get("project_root"),
            "test_file_output_path": default_config.get("test_file_output_path"),
            "code_coverage_report_path": default_config.get("code_coverage_report_path"),
            "test_command": default_config.get("test_command"),
            "test_command_dir": default_config.get("test_command_dir"),
            "included_files": default_config.get("included_files"),
            "coverage_type": default_config.get("coverage_type"),
            "report_filepath": default_config.get("report_filepath"),
            "desired_coverage": default_config.get("desired_coverage"),
            "max_iterations": default_config.get("max_iterations"),
            "max_run_time_sec": default_config.get("max_run_time_sec"),
            "additional_instructions": default_config.get("additional_instructions"),
            "model": default_config.get("model"),
            "api_base": default_config.get("api_base"),
            "strict_coverage": default_config.get("strict_coverage"),
            "run_tests_multiple_times": default_config.get("run_tests_multiple_times"),
            "log_db_path": default_config.get("log_db_path"),
            "branch": default_config.get("branch"),
            "use_report_coverage_feature_flag": default_config.get("use_report_coverage_feature_flag"),
            "diff_coverage": default_config.get("diff_coverage"),
            "run_each_test_separately": default_config.get("run_each_test_separately"),
            "record_mode": default_config.get("record_mode"),
            "suppress_log_files": default_config.get("suppress_log_files"),
            "max_test_files_allowed_to_analyze": default_config.get("max_test_files_allowed_to_analyze"),
            "look_for_oldest_unchanged_test_file": default_config.get("look_for_oldest_unchanged_test_file"),
            "project_language": default_config.get("project_language"),
        }

        # CLI overrides default settings
        merged_dict = default_dict.copy()
        for k, v in args_dict.items():
            if v is not None and hasattr(args, k):
                merged_dict[k] = v

        return cls(**merged_dict)
